/**
 * @File name: modules/shell.c
 * @Synopsis:
 * @Author: xing.dai@aliyun.com
 * @Version: 0.0.1
 * @Date: 2018-08-13 21:18
 */

#include "includes.h"
#include <string.h>

#define getcmd_getch()	getchar()

#define MAX_CMD_LEN					50
#define MAX_CMD_ARG_LEN				50
#define MAX_CMD_RD_LINE_LEN			(MAX_CMD_LEN + MAX_CMD_ARG_LEN)
#define SHELL_CMD_ITEM(cmd)			{cmd, (#cmd), cmd##_cmd_proc}
#define SHELL_CMD_DECLARATION(cmd)	int cmd##_cmd_proc(void *)

typedef struct {
    char cmd_id;
    char cmd_str[MAX_CMD_LEN];
    int (*func)(void *);
} cmd_tree_s;

enum {
    version,
    set_reg,
    get_reg,
    MAX_SHELL_CMD
};

SHELL_CMD_DECLARATION(version);
SHELL_CMD_DECLARATION(set_reg);
SHELL_CMD_DECLARATION(get_reg);

/**
 * @Synopsis  Command sequence need store in flash. use const keywords.
 */
cmd_tree_s const cmd_sequence[] = {
    SHELL_CMD_ITEM(version),
    SHELL_CMD_ITEM(set_reg),
    SHELL_CMD_ITEM(get_reg),
};

int version_cmd_proc(void *arg)
{
    show_info("show version.\n");
    return 0;
}

int set_reg_cmd_proc(void *arg)
{
#ifdef ARCH_PC
    show_info("Linux project, can not get/set register.\n");
    return 0;
#endif
    show_info("%s-> arg:%s.\n", __func__, (char *)arg);
    return 0;
}

/**
 * @Synopsis
 *
 * @Param arg
 *
 * @Returns
 */
int get_reg_cmd_proc(void *arg)
{
    char *arg_s = (char *)arg;
    uint32 i = 0;
    uint32 addr = 0;

#ifdef ARCH_PC
    show_info("Linux project, can not get/set register.\n");
    return 0;
#endif
    if (arg_s[0] == 0x00) {
        show_info("No address input!\n");
        show_info("[Usage] get_reg 0x12345678\n");
        return -1;
    }

    for (i = 0; i < strlen(arg_s); i++)
        if (arg_s[i] == ' ') {
            show_info("Only has 1 arg!\n");
            show_info("[Usage] get_reg 0x12345678\n");
            return -1;
        }

    if (ishexstr(arg_s) != 0) {
        show_info("Arg:%s error. Input arg need hex character.\n", arg_s);
        show_info("[Usage] get_reg 0x12345678\n");
        return -1;
    }
    addr = hexstr2uint32(arg_s);
    show_info("%s-> arg:%s->%x.\n", __func__, arg_s, addr);
    return 0;
}

/**
 * @Synopsis  Read line from console.
 *
 * @Param prompt
 * @Param buf
 * @Param len
 * @Param timeout
 *
 * @Returns
 */
static int read_line(const char *const prompt, char *buf, uint32 *len, int timeout)
{
    int ichar;
    uint32 i = 0;

    for (; ; ) {

        ichar = getcmd_getch();
        if (ichar == -1)
            continue;
        if (ichar == '\n' || ichar == '\r') {
            putc('\n', stdout);
            fflush(stdout);
            break;
        } else {
            putc(ichar, stdout);
            printf("ipt: %c\n", (char)ichar);
            fflush(stdout);
        }

        buf[i] = ichar;
        i++;
    }
    buf[i] = '\0';
    *len = i;
    return 0;
}

/**
 * @Synopsis  Handle command to sequence.
 *
 * @Param cmd
 *
 * @Returns
 */
static int cmd_proc(char *rd_buf)
{
    cmd_tree_s const *cmd_seq_ptr = cmd_sequence;
    uint32 i = 0;
    uint8 res = 0;
    char cmd[MAX_CMD_LEN] = {0};
    char cmd_arg[MAX_CMD_ARG_LEN] = {0};

    trim(rd_buf);

    while (rd_buf[i] != ' ' && rd_buf[i] != '\0') {
        i++;
    }
    strncpy(cmd, rd_buf, i);
    trim(cmd);
    if (i < strlen(rd_buf)) {
        strncpy(cmd_arg, rd_buf + i, strlen(rd_buf) - i);
        trim(cmd_arg);
    }

    for (i = 0; i < MAX_SHELL_CMD; i++) {
        if (strcmp(cmd, cmd_seq_ptr->cmd_str) == 0) {
            res = cmd_seq_ptr->func(cmd_arg);
            return res;
        }
        cmd_seq_ptr++;
    }

    show_info("Cannot parse cmd: %s\n", cmd);
    return 0;
}

/**
 * @Synopsis  shell task.
 *
 * @Param arg
 */
void task_shell_entry(void *arg)
{
    char rd_buf[MAX_CMD_RD_LINE_LEN];
    uint32 rd_len;
    const char *const prompt = "vulture # ";
    for ( ; ; ) {
        sleep_tick(10);

        memset(rd_buf, 0x00, MAX_CMD_RD_LINE_LEN);
        rd_len = 0;
        show_info("\033[0;34m%s\033[0;39m", prompt);	/* Blue character */
        fflush(stdout);
        read_line(prompt, rd_buf, &rd_len, 0);
        if (rd_len == 0)
            continue;
        //show_info("rcv: %s. len:%d.\n", rd_buf, rd_len);
        cmd_proc(rd_buf);

    }

}
